
//
// Authors:
//   Atsushi Enomoto
//
// Copyright 2007 Novell (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if NET_2_0

using System;
using System.Xml;

namespace System.Xml.Linq
{

	internal class XNodeWriter : XmlWriter
	{

		public XNodeWriter(XContainer fragment)
		{
			root = fragment;
			state = XmlNodeType.None;
			current = fragment;
		}
		XContainer root;
		Boolean is_closed;
		// If it is not null, then we are now inside the element.
		XContainer current;
		// If it is not null, then we are now inside the attribute.
		XAttribute attribute;
		// None: started or closed.
		// XmlDeclaration: after xmldecl. Never allow xmldecl.
		// DocumentType: after doctype. Never allow xmldecl and doctype.
		// Element: inside document element.
		// 
		XmlNodeType state;

		// Properties
		public override WriteState WriteState
		{
			get
			{
				if (is_closed)
					return WriteState.Closed;
				if (attribute != null)
					return WriteState.Attribute;

				switch (state)
				{

					case XmlNodeType.None:
						return WriteState.Start;

					case XmlNodeType.XmlDeclaration:
						return WriteState.Prolog;

					case XmlNodeType.DocumentType:
						return WriteState.Element;
					default:
						return WriteState.Content;
				}
			}
		}
		/*

		public override String XmlLang {
			get {

				for (XElement n = current as XElement; n != null; n = n.Parent as XElement)
					if (n.HasAttribute ("xml:lang"))
						return n.GetAttribute ("xml:lang");
				return String.Empty;
			}
		}

		public override XmlSpace XmlSpace {
			get {

				for (XElement n = current as XElement; n != null; n = n.Parent as XElement) {
					String xs = n.GetAttribute ("xml:space");

					switch (xs) {

					case "preserve":
						return XmlSpace.Preserve;

					case "default":
						return XmlSpace.Default;

					case "":
						continue;
					default:
						throw new InvalidOperationException (String.Format ("Invalid xml:space {0}.", xs));
					}
				}
				return XmlSpace.None;
			}
		}
		*/

		// Private Methods
		void CheckState()
		{
			if (is_closed)
				throw new InvalidOperationException();
		}

		void WritePossiblyTopLevelNode(XNode n, Boolean possiblyAttribute)
		{
			CheckState();
			if (!possiblyAttribute && attribute != null)
				throw new InvalidOperationException(String.Format("Current state is not acceptable for {0}.", n.NodeType));
			if (state != XmlNodeType.Element)
				root.Add(n);
			else if (attribute != null)
				attribute.Value += XUtil.ToString(n);
			else
				current.Add(n);
			if (state == XmlNodeType.None)
				state = XmlNodeType.XmlDeclaration;
		}

		// unlike other XmlWriters the callers must set xmlns
		// attribute to overwrite prefix.
		void FillXmlns(XElement el, String prefix, XNamespace xns)
		{
			if (xns == XNamespace.Xmlns)
				// do nothing for xmlns attributes
				return;
			if (prefix == null)
				return;
			if (xns == XNamespace.None)
				if (el.GetPrefixOfNamespace(xns) != prefix)
					el.SetAttributeValue(prefix == String.Empty ? XNamespace.None.GetName("xmlns") : XNamespace.Xmlns.GetName(prefix), xns.NamespaceName);
				else if (el.GetDefaultNamespace() != XNamespace.None)
					el.SetAttributeValue(XNamespace.None.GetName("xmlns"), xns.NamespaceName);
		}

		// Public Methods
		public override void Close()
		{
			CheckState();
			is_closed = true;
		}

		public override void Flush()
		{
		}

		public override String LookupPrefix(String ns)
		{
			CheckState();
			if (current == null)
				throw new InvalidOperationException();
			XElement el = (current as XElement) ?? current.Parent;
			return el != null ? el.GetPrefixOfNamespace(XNamespace.Get(ns)) : null;
		}

		// StartDocument
		public override void WriteStartDocument()
		{
			WriteStartDocument(null);
		}

		public override void WriteStartDocument(Boolean standalone)
		{
			WriteStartDocument(standalone ? "yes" : "no");
		}

		void WriteStartDocument(String sddecl)
		{
			CheckState();
			if (state != XmlNodeType.None)
				throw new InvalidOperationException("Current state is not acceptable for xmldecl.");
			XDocument doc = current as XDocument;
			if (doc == null)
				throw new InvalidOperationException("Only document node can accept xml declaration");

			doc.Declaration = new XDeclaration("1.0", null, sddecl);
			state = XmlNodeType.XmlDeclaration;
		}

		// EndDocument
		public override void WriteEndDocument()
		{
			CheckState();
			is_closed = true;
		}

		// DocumentType
		public override void WriteDocType(String name, String publicId, String systemId, String internalSubset)
		{
			CheckState();

			switch (state)
			{

				case XmlNodeType.None:

				case XmlNodeType.XmlDeclaration:
					XDocument doc = current as XDocument;
					if (doc == null)
						throw new InvalidOperationException("Only document node can accept doctype declaration");

					doc.Add(new XDocumentType(name, publicId, systemId, internalSubset));
					state = XmlNodeType.DocumentType;
					break;
				default:
					throw new InvalidOperationException("Current state is not acceptable for doctype.");
			}
		}

		// StartElement
		public override void WriteStartElement(String prefix, String name, String ns)
		{
			CheckState();
			XNamespace xns = XNamespace.Get(ns ?? String.Empty);
			XElement el = new XElement(xns.GetName(name));
			if (current == null)
			{
				root.Add(el);
				state = XmlNodeType.Element;
			}
			else
			{
				current.Add(el);
				state = XmlNodeType.Element;
			}
			FillXmlns(el, prefix, xns);
			current = el;
		}

		// EndElement
		public override void WriteEndElement()
		{
			WriteEndElementInternal(false);
		}

		public override void WriteFullEndElement()
		{
			WriteEndElementInternal(true);
		}

		void WriteEndElementInternal(Boolean forceFull)
		{
			CheckState();
			if (current == null)
				throw new InvalidOperationException("Current state is not acceptable for endElement.");
			XElement el = current as XElement;
			if (forceFull)
				el.IsEmpty = false;
			current = current.Parent;
		}

		// StartAttribute
		public override void WriteStartAttribute(String prefix, String name, String ns)
		{
			CheckState();
			if (attribute != null)
				throw new InvalidOperationException("There is an open attribute.");
			XElement el = current as XElement;
			if (el == null)
				throw new InvalidOperationException("Current state is not acceptable for startAttribute.");
			if (prefix == null)
				prefix = String.Empty;
			// special case: in XmlWriter context, xmlns="blah" is
			// passeed as localName = "xmlns", ns = w3c_xmlns.
			if (prefix.Length == 0 && name == "xmlns" && ns == XNamespace.Xmlns.NamespaceName)
				ns = String.Empty;
			XNamespace xns = ns == null ? XNamespace.None : XNamespace.Get(ns);
			el.SetAttributeValue(xns.GetName(name), String.Empty);
			attribute = el.LastAttribute;
			FillXmlns(el, ns != null ? prefix : null, xns);
		}

		public override void WriteEndAttribute()
		{
			CheckState();
			if (attribute == null)
				throw new InvalidOperationException("Current state is not acceptable for startAttribute.");
			attribute = null;
		}

		public override void WriteCData(String data)
		{
			CheckState();
			if (current == null)
				throw new InvalidOperationException("Current state is not acceptable for CDATAsection.");
			current.Add(new XCData(data));
		}

		public override void WriteComment(String comment)
		{
			WritePossiblyTopLevelNode(new XComment(comment), false);
		}

		public override void WriteProcessingInstruction(String name, String value)
		{
			WritePossiblyTopLevelNode(
				new XProcessingInstruction(name, value), false);
		}

		public override void WriteEntityRef(String name)
		{
			throw new NotSupportedException();
		}

		public override void WriteCharEntity(Char c)
		{
			throw new NotSupportedException();
		}

		public override void WriteWhitespace(String ws)
		{
			// FIXME: check whitespace
			WritePossiblyTopLevelNode(new XText(ws), true);
		}

		public override void WriteString(String data)
		{
			CheckState();
			if (current == null)
				throw new InvalidOperationException("Current state is not acceptable for Text.");
			if (attribute != null)
				attribute.Value += data;
			else
				current.Add(data);
		}

		public override void WriteName(String name)
		{
			WriteString(name);
		}

		public override void WriteNmToken(String nmtoken)
		{
			WriteString(nmtoken);
		}

		public override void WriteQualifiedName(String name, String ns)
		{
			String prefix = LookupPrefix(ns);
			if (prefix == null)
				throw new ArgumentException(String.Format("Invalid namespace {0}", ns));
			if (prefix != String.Empty)
				WriteString(name);
			else
				WriteString(prefix + ":" + name);
		}

		public override void WriteChars(Char[] chars, Int32 start, Int32 len)
		{
			WriteString(new String(chars, start, len));
		}

		public override void WriteRaw(String data)
		{
			// It never supports raw String.
			WriteString(data);
		}

		public override void WriteRaw(Char[] chars, Int32 start, Int32 len)
		{
			// It never supports raw String.
			WriteChars(chars, start, len);
		}

		public override void WriteBase64(Byte[] data, Int32 start, Int32 len)
		{
			// It never supports raw String.
			WriteString(Convert.ToBase64String(data, start, len));
		}

		public override void WriteBinHex(Byte[] data, Int32 start, Int32 len)
		{
			throw new NotImplementedException();
		}

		public override void WriteSurrogateCharEntity(Char c1, Char c2)
		{
			throw new NotImplementedException();
		}
	}
}

#endif